using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.Diagnostics;

namespace Dida
{
	internal class DynamicTraitWrapperClass
	{
		TypeBuilder wrapperType;

		public DynamicTraitWrapperClass(Type type)
		{
			TraitsAssembly asm = TraitsAssembly.DynamicTraitsAssembly;
			wrapperType = asm.ModuleBuilder.DefineType(
				asm.FindFreeClassName(type.Name));
			wrapperType.SetParent(type);

			FieldBuilder parentField = wrapperType.DefineField("parent",
				typeof(DynamicTrait), FieldAttributes.Public);

			foreach (MethodInfo method in type.GetMethods())
			{
				object[] attrs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				if (attrs.Length == 1)
				{
					CreateProxyMethod(wrapperType, method, parentField);
				}
			}

			wrapperType.CreateType();
		}

		private void CreateProxyMethod(TypeBuilder wrapperType, MethodInfo method,
			FieldBuilder parentField)
		{
			MethodBuilder methodProxy = TraitsAssembly.CreateCompatableMethod(
				wrapperType, method);
			wrapperType.DefineMethodOverride(methodProxy, method);

			ILGenerator ilGen = methodProxy.GetILGenerator();
			LocalBuilder wasImplemented = ilGen.DeclareLocal(typeof(bool));

			// create an object array, that contains all the parameters
			LocalBuilder paramsLocal = ilGen.DeclareLocal(typeof(object[]));
			ParameterInfo[] paramInfos = method.GetParameters();
			ilGen.Emit(OpCodes.Ldc_I4,paramInfos.Length);
			ilGen.Emit(OpCodes.Newarr, typeof(object));
			ilGen.Emit(OpCodes.Stloc, paramsLocal);
			for(int i = 0;i < paramInfos.Length;i++)
			{
				ilGen.Emit(OpCodes.Ldloc,paramsLocal);
				ilGen.Emit(OpCodes.Ldc_I4,i);
				ilGen.Emit(OpCodes.Ldarg, i + 1);
				if (paramInfos[i].ParameterType.IsValueType)
				{
					ilGen.Emit(OpCodes.Box, paramInfos[i].ParameterType);
				}
				ilGen.Emit(OpCodes.Stelem,typeof(object));
			}

			ilGen.Emit(OpCodes.Ldarg_0);
			ilGen.Emit(OpCodes.Ldfld, parentField);
			ilGen.Emit(OpCodes.Ldc_I4, method.MetadataToken);
			ilGen.Emit(OpCodes.Ldloca, wasImplemented);
			ilGen.Emit(OpCodes.Ldloc, paramsLocal);
			ilGen.Emit(OpCodes.Call, typeof(DynamicTrait).GetMethod("CallMethodImplementation"));
			
			if (method.ReturnType == typeof(void))
				ilGen.Emit(OpCodes.Pop);

			if (!method.IsAbstract)
			{
				ilGen.Emit(OpCodes.Ldloc, wasImplemented);
				ilGen.Emit(OpCodes.Ldc_I4_0);
				Label skipBaseCall = ilGen.DefineLabel();
				ilGen.Emit(OpCodes.Bne_Un, skipBaseCall);

				// pop the return value from CallMethodImplementation
				// (if it was void, it had been popped earlier)
				if(method.ReturnType != typeof(void))
					ilGen.Emit(OpCodes.Pop);

				// load the params for the base class' version call
				ilGen.Emit(OpCodes.Ldarg_0);		// load the this ptr
				int nParams = method.GetParameters().Length;
				for (int i = 0; i < nParams; i++)
				{
					ilGen.Emit(OpCodes.Ldarg, i + 1);
				}

				// call the base class version.
				ilGen.Emit(OpCodes.Call, method);

				ilGen.MarkLabel(skipBaseCall);
			}

			if(method.ReturnType.IsValueType && method.ReturnType != typeof(void))
			{
				ilGen.Emit(OpCodes.Unbox_Any, method.ReturnType);
			}

			ilGen.Emit(OpCodes.Ret);
		}

		public Trait CreateInstance(DynamicTrait parent)
		{
			object ret = Activator.CreateInstance(wrapperType);
			wrapperType.GetField("parent").SetValue(ret, parent);
			return (Trait)ret;
		}

		static Dictionary<Type, DynamicTraitWrapperClass> wrappers = 
			new Dictionary<Type,DynamicTraitWrapperClass>();
		public static DynamicTraitWrapperClass GetWrapper(Type type)
		{
			DynamicTraitWrapperClass ret;
			if (wrappers.TryGetValue(type, out ret))
				return ret;
			else
			{
				ret = new DynamicTraitWrapperClass(type);
				wrappers.Add(type, ret);
				return ret;
			}
		}
	}

	/// <summary>
	/// A trait that can be bound and unbound to other traits at any moment.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class is similar to a <see cref = "Trait"/> in a trait class, 
	/// with the difference that the binding to methods of other traits
	/// is dynamic. This means that while the trait exists, it can be bound
	/// to other traits, without losing it's state.
	/// </para>
	/// <para>
	/// The downside of this class is that it's a lot slower than precompiled
	/// traits, so in the final game, it's recommanded to use precompiled traits
	/// instead.
	/// </para>
	/// </remarks>
	public class DynamicTrait
	{
		Type type;

		struct MethodImplInfo
		{
			public DynamicTrait implementingTrait;
			public MethodInfo implementingMethod;
		}
		Dictionary<int, MethodImplInfo[]> methodImpls = 
			new Dictionary<int, MethodImplInfo[]>();
		Trait wrapper;
		
		public DynamicTrait(Type type)
		{
			Debug.Assert(type.IsSubclassOf(typeof(Trait)),"A trait must be derived from the Trait class.");

			this.type = type;
			wrapper = DynamicTraitWrapperClass.GetWrapper(type).CreateInstance(this);
		}
		
		/// <internal/>
		public object CallMethodImplementation(int metadataToken, ref bool wasImplemented,
			object[] args)
		{
			MethodImplInfo[] implInfos;
			if (methodImpls.TryGetValue(metadataToken, out implInfos))
			{
				wasImplemented = true;
				object returnValue = null;
				foreach (MethodImplInfo impl in implInfos)
				{
					returnValue = impl.implementingMethod.Invoke(
						impl.implementingTrait.wrapper, args);
				}
				return returnValue;
			}
			else
			{
				wasImplemented = false;
				return null;
			}
		}

		public Type Type
		{
			get { return type; }
		}

		public Trait TraitObject
		{
			get { return wrapper; }
		}

		/// <summary>
		/// Recalculates the connections of methods declared in this trait
		/// </summary>
		/// <param name="resolver">The <see cref = "TraitMethodResolver"/>
		/// that should contain the information used to resolve the traits.</param>
		internal void UpdateConnections(TraitMethodResolver resolver)
		{
			methodImpls.Clear();

			foreach (MethodInfo method in type.GetMethods())
			{
				object[] attrs = method.GetCustomAttributes(
					typeof(TraitDeclareMethodAttribute), true);
				if (attrs.Length == 1)
				{
					IList<TraitMethodResolver.Implementation> implementations = 
						resolver.GetMethodImplementations(method);

					if (implementations.Count > 0)
					{
						MethodImplInfo[] implInfos = new MethodImplInfo[implementations.Count];
						for (int i = 0; i < implementations.Count; i++)
						{
							TraitMethodResolver.Implementation curImpl = implementations[i];

							MethodImplInfo implInfo;
							implInfo.implementingTrait = (DynamicTrait)curImpl.ImplementingTraitUserData;
							implInfo.implementingMethod = curImpl.MethodInfo;
							implInfos[i] = implInfo;
						}
						methodImpls[method.MetadataToken] = implInfos;
					}
				}
			}
		}
	}

	public class DynamicTraitClass
	{
		TraitMethodResolver resolver = new TraitMethodResolver();
		Dictionary<string,DynamicTrait> traits = new Dictionary<string,DynamicTrait>();

		public DynamicTrait AddTrait(string name,Type traitType)
		{
			DynamicTrait trait = new DynamicTrait(traitType);
			traits.Add(name,trait);
			resolver.AddTrait(name, trait.Type,trait);
			Update();
			return trait;
		}

		public DynamicTrait[] AddTraits(string[] names, Type[] types)
		{
			Debug.Assert(names.Length == types.Length, "The number of items in the names array must match the number of items in the types list");

			DynamicTrait[] ret = new DynamicTrait[names.Length];

			for (int i = 0; i < names.Length; i++)
			{
				DynamicTrait trait = new DynamicTrait(types[i]);
				traits.Add(names[i], trait);
				resolver.AddTrait(names[i], types[i], trait);

				ret[i] = trait;
			}
			
			Update();

			return ret;
		}

		public void RemoveTrait(string name)
		{
			resolver.RemoveTrait(name);
			traits.Remove(name);
		}

		public void RenameTrait(string oldName,string newName)
		{
			resolver.RenameTrait(oldName, newName);

			DynamicTrait backup = traits[oldName];
			traits.Remove(oldName);
			traits.Add(newName, backup);
		}

		private void Update()
		{
			if (!resolver.HasErrors)
			{
				foreach (DynamicTrait trait in traits.Values)
					trait.UpdateConnections(resolver);
			}
		}

		public bool HasErrors
		{
			get { return resolver.HasErrors; }
		}

		public T GetTrait<T>()
			where T : Trait
		{
			return (T)GetTrait(typeof(T));
		}

		public Trait GetTrait(Type traitType)
		{
			foreach(DynamicTrait trait in traits.Values)
			{
				if (trait.Type == traitType)
					return trait.TraitObject;
			}

			throw new Exception("Trait of type " + traitType.FullName + " not found.");
		}

		/*public T DuckTypeInterface<T>()
		{
			TraitsAssembly asm = TraitsAssembly.DynamicTraitsAssembly;
			TypeBuilder implType = asm.ModuleBuilder.DefineType(
				asm.FindFreeClassName(typeof(T).Name));
			implType.SetParent(typeof(DucktypeImplBase));
			implType.AddInterfaceImplementation(typeof(T));

			FieldBuilder traitClassField = implType.DefineField("traitClass",
				typeof(DynamicTraitClass),FieldAttributes.Public);

			MethodInfo[] methods = typeof(T).GetMethods();
			foreach (MethodInfo interfaceMethod in methods)
			{
				ParameterInfo[] parameters = interfaceMethod.GetParameters();
				Type[] paramTypes = new Type[parameters.Length];
				for(int i = 0;i < parameters.Length;i++)
					paramTypes[i] = parameters[i].ParameterType;
				MethodBuilder method = implType.DefineMethod(interfaceMethod.Name,
					MethodAttributes.Public|MethodAttributes.Virtual,
					interfaceMethod.ReturnType, paramTypes);
				implType.DefineMethodOverride(method, interfaceMethod);

				ILGenerator ilGen = method.GetILGenerator();

				ilGen.Emit(OpCodes.Ldarg_0);
				ilGen.Emit(OpCodes.Ldfld,traitClassField);
				
				// create the arguments array
				ilGen.Emit(OpCodes.Ldc_I4,paramTypes.Length);
				ilGen.Emit(OpCodes.Newarr,typeof(object));
				for(int i = 0;i < paramTypes.Length;i++)
				{
					ilGen.Emit(OpCodes.Dup);
					ilGen.Emit(OpCodes.Ldc_I4,i);
					ilGen.Emit(OpCodes.Ldarg,0);
					if(paramTypes[i].IsValueType)
						ilGen.Emit(OpCodes.Box,paramTypes[i]);
					ilGen.Emit(OpCodes.Stelem,typeof(object));
				}

				ilGen.Emit(OpCodes.Call, typeof(DucktypeImplBase).GetMethod("CallMethod"));
				if (method.ReturnType == typeof(void))
					ilGen.Emit(OpCodes.Pop);
				else if (method.ReturnType.IsValueType)
					ilGen.Emit(OpCodes.Unbox_Any, method.ReturnType);
				ilGen.Emit(OpCodes.Ret);
			}

			implType.CreateType();
			DucktypeImplBase ret = (DucktypeImplBase)Activator.CreateInstance(implType);
			ret.traitClass = this;

			return (T)(object)ret;
		}

		/// <internal/>
		public class DucktypeImplBase
		{
			public DynamicTraitClass traitClass;

			public object CallMethod(object[] args)
			{
				Console.WriteLine("Hi");
				return null;
			}
		}*/
	}
}